# --------------------------------------------------------------------------------------------------------------------
# Project:             Digital Payment System (DPS)
# Website:             http://code.google.com/p/dps-x509/
# Purpose of document: This class constructs an x.509 certificate
# --------------------------------------------------------------------------------------------------------------------
import sys, string, logging, base64, os
from time import time

sys.path.append("..")

from pyasn1.type import base, univ
from pyasn1 import error

try:
  import keyczar.keyinfo as keyinfo
  import keyczar.keys as keys
  import keyczar.util as util
except ImportError:
  from keyczar import *

import dps.asn1CertificateSpecification
import dps.csrParams
import dps.errors
import dps.x509Asn1Extractor
import dps.x509AttributeAndValue
import dps.x509Extension
import dps.x509Helper
import dps.utils

# --------------------------------------------------------------------------------------------------------------------
TEMPLATE_FILE = """
MIIBnTCCAQYCAQAwXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIw
EAYDVQQDEwlsb2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5l
eGFtcGxlLmRvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAr1nYY1Qrll1r
uB/FqlCRrr5nvupdIN+3wF7q915tvEQoc74bnu6b8IbbGRMhzdzmvQ4SzFfVEAuM
MuTHeybPq5th7YDrTNizKKxOBnqE2KYuX9X22A1Kh49soJJFg6kPb9MUgiZBiMlv
tb7K3CHfgw5WagWnLl8Lb+ccvKZZl+8CAwEAAaAAMA0GCSqGSIb3DQEBBAUAA4GB
AHpoRp5YS55CZpy+wdigQEwjL/wSluvo+WjtpvP0YoBMJu4VMKeZi405R7o8oEwi
PdlrrliKNknFmHKIaCKTLRcU59ScA6ADEIWUzqmUzP5Cs6jrSRo3NKfg1bd09D1K
9rsQkRc9Urv9mRBIsredGnYECNeRaK5R1yzpOowninXC
"""

# --------------------------------------------------------------------------------------------------------------------
class CsrAsn1Creator:
  """ 
  Responsible for making ASN.1 data from python objects. We cheat a bit here. 
  Rather than building a document from scratch we take a copy of a template certificate and modify
  its contents. This was largely due to time constraints

  important conversions to know:

  We are dealing with 3 main datatypes:
    - Asn1:   U{http://en.wikipedia.org/wiki/Asn1}
    - Bytes:  U{http://en.wikipedia.org/wiki/Bytes}
    - Base64: U{http://en.wikipedia.org/wiki/Base64}  
  """    

# --------------------------------------------------------------------------------------------------------------------
  def __getSubjectPublicKeyAsn(self, privateKey):
    """ create the private keys matching public key """
    mod = util.BytesToLong(privateKey.public_key.params["modulus"])
    publicExp = util.BytesToLong(privateKey.public_key.params["publicExponent"])
    params = {"n": mod, "e": publicExp}
    subjectPublicKeyInfoAsn = dps.x509Helper.bytesToAsn1(dps.x509Helper.base64ToBytes(util.ExportRsaX509(params), True), dps.asn1CertificateSpecification.SubjectPublicKeyInfo())
    return subjectPublicKeyInfoAsn

  # --------------------------------------------------------------------------------------------------------------------
  def __getTemplateCertificateAsn(self):
    """ copy an existing certificate from file """
    certs = dps.x509Helper.certsToBase64(TEMPLATE_FILE)
    dps.utils.assertion(len(certs) == 1, \
                        "x509Asn1Creator.__getTemplateCertificateAsn: Could not find exactly one certificate")
    certBytes = dps.x509Helper.base64ToBytes(certs[0])
    certAsn = dps.x509Helper.bytesToAsn1(certBytes)
    return certAsn

  # --------------------------------------------------------------------------------------------------------------------
  def __setSubjectPublicKeyInfo(self, subjectPublicKeyInfoAsn):
    """ 
    sets the public key info for the certifcate 
    @param subjectPublicKeyInfoAsn: public key info in asn1 format 
    @type  subjectPublicKeyInfoAsn: base.Asn1Item
    """
    dps.utils.assertion(isinstance(subjectPublicKeyInfoAsn, base.Asn1Item), \
                        "CsrAsn1Creator.__setSubjectPublicKeyInfo: Incorrect subjectPublicKeyInfoAsn type")

    self.__certAsn_[0].setComponentByPosition(2, subjectPublicKeyInfoAsn)

  # --------------------------------------------------------------------------------------------------------------------
  def __setSubjectAsn(self, subjectAsn1):
    """ sets the subject for the certificate
    @param subjectAsn1: in asn1 format """
    dps.utils.assertion(isinstance(subjectAsn1, base.Asn1Item), \
                        "CsrAsn1Creator.__setSubjectPublicKeyInfo: Incorrect subjectAsn1 type")
    
    self.__certAsn_[0].setComponentByPosition(1, subjectAsn1)
    
# --------------------------------------------------------------------------------------------------------------------
  def __signWithPrivateKey(self, privateKey):
    """ 
    signs the certificate with keyczar.keys.RsaPrivateKey. this includes adding the signed data into the cert asn1
    @raise dps.errors.RsaPrivateKeySigningError: if unable to sign certificate
    """
    dps.utils.assertion(isinstance(privateKey, keys.RsaPrivateKey), \
                        "CsrAsn1Creator.signWithPrivateKey: Incorrect privateKey type")

    try:
      tbsCertificateAsn = self.__certAsn_[0]
      
      certBytes = dps.x509Helper.asn1ToBytes(tbsCertificateAsn)
      sigBytes = privateKey.Sign(certBytes)
      sigBin = util.BytesToBin(sigBytes)
      signatureValue = univ.BitString("'%s'B" % sigBin)  # needs to be a BIT STRING  

      self.__certAsn_.setComponentByPosition(2, signatureValue)
      self.__isSigned_ = True

    except Exception, e:      
      dps.utils.out("Exception in CsrAsn1Creator.signWithPrivateKey: " + str(sys.exc_info()[0]) + str(e))      
      raise dps.errors.RsaPrivateKeySigningError()
    
# --------------------------------------------------------------------------------------------------------------------
  def __init__(self, params, privateKey= None):
    """ 
    create a certificate with params
    @param params: data to but in the csr
    @type  params: dps.x509.certificateParams.X509CertificateParams
    @param privateKey:private key used to sign the csr
    @type  privateKey:privateKey:keyczar.keys.RsaPrivateKey
    @raise dps.errors.RsaPrivateKeyCreateError: if unable to create the certifcate 
    """
    dps.utils.assertion(isinstance(params, dps.csrParams.CsrParams), \
                        "CsrAsn1Creator.__init__: Incorrect params type")
    #this next assert would be nice by i haven't worked out python namespace resolution yet...
    #dps.utils.assertion(not privateKey or isinstance(params, keys.RsaPrivateKey), \
    #                    "CsrAsn1Creator.__init__: Incorrect privateKey type")    
    try:
      self.__certAsn_    = None
      self.__isSigned_   = False
      self.__privateKey_ = privateKey    

      subjectPublicKeyInfoAsn = self.__getSubjectPublicKeyAsn(self.__privateKey_)

      #copy from template
      self.__certAsn_ = self.__getTemplateCertificateAsn()
      
      if not params.getSubject():
        raise dps.errors.MissingRequiredParametersError("No subject data")      
      self.__setSubjectAsn(params.getSubject().asAsn1())

      self.__certAsn_[1].setComponentByPosition(0, univ.ObjectIdentifier("1.2.840.113549.1.1.5"))
      
      #add the new public key to the certificate
      self.__setSubjectPublicKeyInfo(subjectPublicKeyInfoAsn)
      
      self.__signWithPrivateKey(self.__privateKey_)
      self.__isSigned_ = True

    except dps.errors.DPSBaseError:
      raise     
    except Exception, e:      
      dps.utils.out("Exception in CsrAsn1Creator.__init__: " + str(sys.exc_info()[0]) + str(e))      
      raise dps.errors.CertificateCreateErrorError()

  # --------------------------------------------------------------------------------------------------------------------
  def getAsn1(self):
    """ 
    get entire certificate in asn1 format
    @raise dps.errors.CertificateNotSignedError: if the certificate has yet to be signed 
    @rtype: asn1
    """
    if self.__isSigned_:
      return self.__certAsn_
    else:
      raise dps.errors.CertificateNotSignedError()


  # --------------------------------------------------------------------------------------------------------------------
  def getBase64(self):
    """ 
    get entire certificate in base64 format
    @raise dps.errors.CertificateNotSignedError: if the certificate has yet to be signed 
    @rtype: string
    """
    if self.__isSigned_:
      certBase64 = dps.x509Helper.bytesToBase64(dps.x509Helper.asn1ToBytes(self.__certAsn_))

      dps.utils.assertion(isinstance(certBase64, str), \
                          "CsrAsn1Creator.getBase64: Incorrect certBase64 type")
      return certBase64
    else:
      raise dps.errors.CertificateNotSignedError()

# --------------------------------------------------------------------------------------------------------------------
  def getRsaPrivateKey(self):
    """ 
    get the private key from the certificate
    @rtype: keyczar.keys.RsaPrivateKey
    """
    return self.__privateKey_

# --------------------------------------------------------------------------------------------------------------------
if __name__ == "__main__":  
  try:
    privateKeyStr = """{"primeExponentP": 
    "ANe7sE57jbmYMNCvsgpZ0yhf9LPASbTv0dGCXL8KE9qTJRpkW-F56l56Kph-YTI
     1wM1lGedrAUmxpygK34ID8yMyv1qEI-f1017Rz1rmjERGzD3RDQlqkBeR_diOl0
     6LYWTq9NQLh-1C_5pfB5BpMQXuPzpeGfmQlLt_pJIgS7VB", 
     "primeExponentQ": 
    "AHYSYRiyfum_Q4yyjD8fe2TANG6bIq7idpnkG7dpFZNcMQRZpDx0qmu3bTNFTSf
    6fl0b4_LUUWN7xi0FHtWCCAzeG9tvgSqrZre0qXQNPUuCxeTS7xF4R8DGXLC8NpV
    S4N46txwyi1jHsf2f-A9qPEtzPbCfz8-UxRk92_Q8BCWL", 
    "crtCoefficient": 
    "AIIgyy5F4CmrUai1NHGCvuB5GcnEjdkoaqqXbLQtWw1ILgSm6quSVE3N0-723_D
    _LuJEdT5zSAKfCSfHy5MU5W-nY9Nb6uftWzcSvEcY42sbsyHkRJfE_wQSqAr3e7w
    _iot0iMcRlGVy84CbSw5zwaE2EOPjECcVcxzq7IOFi0pT", 
    "privateExponent": 
    "ABlMjag9ug_WmUogKUOT02b8VEeRD9ytEv2KcHlKsN7VHkYSC4CJIrI0pTO0dJF
    NdGnO64Xncut33Ng55aU6hk8UuvwjmMQEm2uoZccJFWnUeYSHyZRqyr5CymUCCK1
    eWekie8tOvrgUaRYG6SOkFwFeb_sNH33IfUtf0qw-UKV-Nj6ct8ASE_M9IQwndV4
    qVFnBxhAqbMSq8tCoYwJVvJ0bOduDu1K7a_ujqlLqNdQjrnnFV4GlG1ZnpaXleCi
    4h3Zbwe2miCwM4xvhhh6lJ52xu3rewcjDYDUv0s4Mv5r30WN2Q1Wr46gYrfz_cVf
    0bxVaymspDR5lOJ1ZI2_X8YE", 
    "publicKey": 
    {"publicExponent": "AAEAAQ", 
    "modulus": 
    "AJ3codzWcyHSpKO7JymiCawALVVEvr6UIxCVtJSQP8yrCOzsRWHtBLAovSaTXy
    ytlammrWqo7ovUCXWS5h4t3E6O7kCnbC_OdVfELCbfhTnir8LN6gUHtMI_07tfk
    A9hdDoeAK1o4cWgoO2ABCgywyx4DOffTac6vkKRdxGMiajdpCvSJf_k-qXe_9A4
    yTG87hOqsfQhCD08D7WkOdfuBvZ300fYjYPDnUlS2x8jAG8p8bwGP1NWSUomGHZ
    cBaex_1HP0qiO49Xux2Ot_zgKMmf7Jh2XjawswUTx9RlGoIOzvUBNwqBChjPUGT
    OCxgZd4Y4JA24KUsXF5zvRJ2250Hs", "size": 2048}, 
    "primeQ": 
    "AKYMqc7rWRapU3kBL6_Q8iydlJi8wwdDXQAeh8jIx6JbgwnBJbDpDDbKcD7yVx
    P_7MrwshGGiohXunq1bPoWm8yMnTsZt9bNYrIghD37GKRofjcU8ic7CKWgw0EH5
    r0U1nK72aZYV9NSccEnn1sfEydm5Jp74SreWGwr0oD5lIu7", 
    "primeP": 
    "APNgh498oKgdw8yVpZd_jzKN8geFsXyATQAMnPlKzJ_1tFfgZ6wEBKxApIGLYQ
    aH0d3bM2OeOgWlDsY__hUkHkL7U69WR_rcTpVm7b791ScSAPafr5jPcKrZY9z-s
    MtJDM_JnG4U5YYCHDfIe9gdFzlXbh9WcsIuM5FgC4AjYqJB", 
    "size": 2048}"""
    privateKeyStr = """{"primeExponentP": 
    "ANe7sE57jbmYMNCvsgpZ0yhf9LPASbTv0dGCXL8KE9qTJRpkW-F56l56Kph-YTI
     1wM1lGedrAUmxpygK34ID8yMyv1qEI-f1017Rz1rmjERGzD3RDQlqkBeR_diOl0
     6LYWTq9NQLh-1C_5pfB5BpMQXuPzpeGfmQlLt_pJIgS7VB", 
     "primeExponentQ": 
    "AHYSYRiyfum_Q4yyjD8fe2TANG6bIq7idpnkG7dpFZNcMQRZpDx0qmu3bTNFTSf
    6fl0b4_LUUWN7xi0FHtWCCAzeG9tvgSqrZre0qXQNPUuCxeTS7xF4R8DGXLC8NpV
    S4N46txwyi1jHsf2f-A9qPEtzPbCfz8-UxRk92_Q8BCWL", 
    "crtCoefficient": 
    "AIIgyy5F4CmrUai1NHGCvuB5GcnEjdkoaqqXbLQtWw1ILgSm6quSVE3N0-723_D
    _LuJEdT5zSAKfCSfHy5MU5W-nY9Nb6uftWzcSvEcY42sbsyHkRJfE_wQSqAr3e7w
    _iot0iMcRlGVy84CbSw5zwaE2EOPjECcVcxzq7IOFi0pT", 
    "privateExponent": 
    "ABlMjag9ug_WmUogKUOT02b8VEeRD9ytEv2KcHlKsN7VHkYSC4CJIrI0pTO0dJF
    NdGnO64Xncut33Ng55aU6hk8UuvwjmMQEm2uoZccJFWnUeYSHyZRqyr5CymUCCK1
    eWekie8tOvrgUaRYG6SOkFwFeb_sNH33IfUtf0qw-UKV-Nj6ct8ASE_M9IQwndV4
    qVFnBxhAqbMSq8tCoYwJVvJ0bOduDu1K7a_ujqlLqNdQjrnnFV4GlG1ZnpaXleCi
    4h3Zbwe2miCwM4xvhhh6lJ52xu3rewcjDYDUv0s4Mv5r30WN2Q1Wr46gYrfz_cVf
    0bxVaymspDR5lOJ1ZI2_X8YE", 
    "publicKey": 
    {"publicExponent": "AAEAAQ", 
    "modulus": 
    "AJ3codzWcyHSpKO7JymiCawALVVEvr6UIxCVtJSQP8yrCOzsRWHtBLAovSaTXy
    ytlammrWqo7ovUCXWS5h4t3E6O7kCnbC_OdVfELCbfhTnir8LN6gUHtMI_07tfk
    A9hdDoeAK1o4cWgoO2ABCgywyx4DOffTac6vkKRdxGMiajdpCvSJf_k-qXe_9A4
    yTG87hOqsfQhCD08D7WkOdfuBvZ300fYjYPDnUlS2x8jAG8p8bwGP1NWSUomGHZ
    cBaex_1HP0qiO49Xux2Ot_zgKMmf7Jh2XjawswUTx9RlGoIOzvUBNwqBChjPUGT
    OCxgZd4Y4JA24KUsXF5zvRJ2250Hs", "size": 2048}, 
    "primeQ": 
    "AKYMqc7rWRapU3kBL6_Q8iydlJi8wwdDXQAeh8jIx6JbgwnBJbDpDDbKcD7yVx
    P_7MrwshGGiohXunq1bPoWm8yMnTsZt9bNYrIghD37GKRofjcU8ic7CKWgw0EH5
    r0U1nK72aZYV9NSccEnn1sfEydm5Jp74SreWGwr0oD5lIu7", 
    "primeP": 
    "APNgh498oKgdw8yVpZd_jzKN8geFsXyATQAMnPlKzJ_1tFfgZ6wEBKxApIGLYQ
    aH0d3bM2OeOgWlDsY__hUkHkL7U69WR_rcTpVm7b791ScSAPafr5jPcKrZY9z-s
    MtJDM_JnG4U5YYCHDfIe9gdFzlXbh9WcsIuM5FgC4AjYqJB", 
    "size": 2048}"""
    privateKey = keys.RsaPrivateKey.Read(privateKeyStr.replace("\n",""))

    params = dps.csrParams.CsrParams()

    #create issuer
    subject = dps.x509AttributeAndValue.X509AttributeAndValueList()
    subjectItem = dps.x509AttributeAndValue.X509AttributeAndValue("Roulette dealer")
    subject.addItem(subjectItem)
    params.setSubject(subject)
    c = CsrAsn1Creator(params, privateKey)

    certBase64 = c.getBase64()
    print dps.x509Helper.prettyBase64(certBase64)
    util.WriteFile(certBase64, "my_dealer.crt")

  except Exception, e:
    dps.utils.out("An exception occured: " + str(sys.exc_info()[0]) + " " + str(e))


